﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Runtime.Caching;
using System.Runtime.Caching.Configuration;
using Memcached.ClientLibrary;

namespace Catalyst.Caching
{
	public class Memcached : ObjectCache
	{
		static Dictionary<string, SockIOPool> _socketPools;
		static Dictionary<string, MemcachedClient> _caches;

		public Memcached()
		{
			if (_caches == null && _socketPools == null)
			{
				MemcachedSection configSection = (MemcachedSection)ConfigurationManager.GetSection("system.runtime.caching/memcached");
				Initialize(configSection);
			}
		}

		public static void Initialize(MemcachedSection config)
		{
			_socketPools = new Dictionary<string, SockIOPool>();
			_caches = new Dictionary<string, MemcachedClient>();

			string name = null;

			foreach (SocketPoolElement element in config.SocketPools)
			{
				name = element.Name;

				if (String.IsNullOrEmpty(name))
				{
					name = "default";
				}

				SockIOPool pool = SockIOPool.GetInstance(name);
				
				List<string> servers = new List<string>();

				foreach (ServerElement server in element.Servers)
				{
					servers.Add(String.Format("{0}:{1}", server.Address, server.Port));
				};

				pool.SetServers(servers.ToArray());

				pool.InitConnections = element.InitConnections;
				pool.MinConnections = element.MinConnections;
				pool.MaxConnections = element.MaxConnections;
				pool.MaxBusy = element.MaxThreadBusyTime;
				pool.MaxIdle = element.MaxThreadIdleTime;
				pool.SocketConnectTimeout = element.SocketConnectionTimeout;
				pool.SocketTimeout = element.SocketTimeout;
				pool.MaintenanceSleep = element.MaintenanceSleep;
				pool.Failover = element.Failover;
				pool.Nagle = element.Nagle;
				pool.Initialize();

				_socketPools.Add(name, pool);
				_caches.Add(name, new MemcachedClient { PoolName = name });
			}
		}

		internal MemcachedClient GetCache(string regionName = null)
		{
			if (regionName == null)
			{
				regionName = "default";
			}

			return _caches[regionName];
		}

		public override bool Add(CacheItem item, CacheItemPolicy policy)
		{
			return Add(item.Key, item.Value, policy, item.RegionName);
		}

		public virtual bool Add(string key, object value, string regionName = null)
		{
			return Add(key, value, ObjectCache.InfiniteAbsoluteExpiration, regionName);
		}

		public override bool Add(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			return Add(key, value, policy.AbsoluteExpiration, regionName);
		}

		public override bool Add(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			MemcachedClient cache = GetCache(regionName);
			return cache.Add(key, value, absoluteExpiration.DateTime);
		}

		public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy)
		{
			return new CacheItem(value.Key, AddOrGetExisting(value.Key, value.Value, policy, value.RegionName));
		}

		public virtual object AddOrGetExisting(string key, object value, string regionName = null)
		{
			return AddOrGetExisting(key, value, ObjectCache.InfiniteAbsoluteExpiration);
		}

		public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			return AddOrGetExisting(new CacheItem(key, value), policy).Value;
		}

		public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			if (!Contains(key))
			{
				Add(key, value, absoluteExpiration);
			}

			return Get(key);
		}

		public override bool Contains(string key, string regionName = null)
		{
			return GetCache(regionName).KeyExists(key);
		}

		public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable<string> keys, string regionName = null)
		{
			throw new NotImplementedException();
		}

		public override DefaultCacheCapabilities DefaultCacheCapabilities
		{
			get
			{
				return DefaultCacheCapabilities.AbsoluteExpirations
					| DefaultCacheCapabilities.SlidingExpirations 
					| DefaultCacheCapabilities.OutOfProcessProvider 
					| DefaultCacheCapabilities.CacheRegions 
					| DefaultCacheCapabilities.CacheEntryUpdateCallback;
			}
		}

		public override object Get(string key, string regionName = null)
		{
			MemcachedClient cache = GetCache(regionName);
			return cache.Get(key);
		}

		public override CacheItem GetCacheItem(string key, string regionName = null)
		{
			return new CacheItem(key, Get(key));
		}

		public override long GetCount(string regionName = null)
		{
			throw new NotImplementedException();
		}

		protected override IEnumerator<KeyValuePair<string, object>> GetEnumerator()
		{
			throw new NotImplementedException();
		}

		public override IDictionary<string, object> GetValues(string regionName, params string[] keys)
		{
			return GetValues(keys, regionName);
		}

		public override IDictionary<string, object> GetValues(IEnumerable<string> keys, string regionName = null)
		{
			Dictionary<string, object> vals = new Dictionary<string, object>();

			foreach (DictionaryEntry item in GetCache(regionName).GetMultiple(keys.ToArray()))
			{
				vals.Add(item.Key.ToString(), item.Value);
			}

			return vals;
		}

		public override string Name
		{
			get { throw new NotImplementedException(); }
		}

		public override object Remove(string key, string regionName = null)
		{
			GetCache(regionName).Delete(key);

			return null;
		}

		public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null)
		{
			Set(key, value, policy.AbsoluteExpiration, regionName);
		}

		public override void Set(CacheItem item, CacheItemPolicy policy)
		{
			Set(item.Key, item.Value, policy.AbsoluteExpiration, item.RegionName);
		}

		public virtual void Set(string key, object value, string regionName = null)
		{
			Set(key, value, ObjectCache.InfiniteAbsoluteExpiration, regionName);
		}

		public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null)
		{
			MemcachedClient cache = GetCache(regionName);
			cache.Set(key, value, absoluteExpiration.DateTime);
		}

		public override object this[string key]
		{
			get
			{
				return GetCache().Get(key);
			}
			set
			{
				GetCache().Set(key, value);
			}
		}
	}
}
